using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;

namespace HQLAddin.Highlighting.Tagger
{

	//This code was ported from PkgDefEditor Example, by careful, 
	//there are not tests for this yet.

	[Export(typeof (IViewTaggerProvider))]
	[ContentType("xml")]
	[TagType(typeof (ErrorTag))]
	internal class ErrorTaggerProvider : IViewTaggerProvider
	{
		[Import(typeof (SVsServiceProvider))] internal IServiceProvider ServiceProvider;

		[Import] internal ITextDocumentFactoryService TextDocumentFactoryService;

		[Import] internal IBufferTagAggregatorFactoryService AggregatorFactory;

		#region IViewTaggerProvider Members

		public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
		{
			if (textView.TextBuffer != buffer)
				return null;

			if (!typeof(ITagger<T>).IsAssignableFrom(typeof(ErrorTagger)))
				return null;

			return textView.Properties.GetOrCreateSingletonProperty(
				() => new ErrorTagger(buffer, ServiceProvider, new ErrorAnalizer(), TextDocumentFactoryService) as ITagger<T>);
		}

		#endregion
	}

	internal class ErrorTagger : ITagger<ErrorTag>, IDisposable
	{
		private readonly IErrorAnalizer analizer;
		private readonly ITextBuffer buffer;
		private readonly ITextDocument document;
		private readonly ErrorListProvider errorListProvider;

		public ErrorTagger(ITextBuffer buffer, 
							IServiceProvider serviceProvider, 
							IErrorAnalizer analizer,
							ITextDocumentFactoryService textDocumentFactory)
		{	
			this.buffer = buffer;
			this.analizer = analizer;
			if (!textDocumentFactory.TryGetTextDocument(buffer, out document)) document = null;

			errorListProvider = new ErrorListProvider(serviceProvider);
			ReparseFile(this, EventArgs.Empty);

			BufferIdleEventUtil.AddBufferIdleEventListener(buffer, ReparseFile);
		}

		#region IDisposable Members

		public void Dispose()
		{
			BufferIdleEventUtil.RemoveBufferIdleEventListener(buffer, ReparseFile);
			errorListProvider.Tasks.Clear();
			errorListProvider.Dispose();
		}

		#endregion

		#region ITagger<ErrorTag> Members

		public IEnumerable<ITagSpan<ErrorTag>> GetTags(NormalizedSnapshotSpanCollection spans)
		{
			return GetIntersectingLineSpans(spans)
					.SelectMany(curSpan => analizer.GetMatches(curSpan));
		}

		/// <summary>
		/// Get all sets of lines that intersect a <see cref="NormalizedSnapshotSpanCollection" />.
		/// </summary>
		/// <param name="spans">The normalized set of spans.</param>
		/// <returns>An enumeration of text snapshot spans.</returns>
		internal static IEnumerable<SnapshotSpan> GetIntersectingLineSpans(NormalizedSnapshotSpanCollection spans)
		{
			var lastSpan = new SnapshotSpan();

			foreach (SnapshotSpan span in spans)
			{
				if (!lastSpan.IsEmpty && lastSpan.End >= span.End)
					continue;

				// If we haven't yet returned the first set of lines for this span, do so
				if (lastSpan.IsEmpty || lastSpan.End <= span.Start)
				{
					lastSpan = GetContinuedLines(span.Start.GetContainingLine());
					yield return lastSpan;
				}

				// Cycle through the rest of the lines
				while (lastSpan.End < span.End && lastSpan.End.GetContainingLine().LineNumber < lastSpan.Snapshot.LineCount)
				{
					lastSpan = GetContinuedLines(lastSpan.Snapshot.GetLineFromLineNumber(lastSpan.End.GetContainingLine().LineNumber + 1));
					yield return lastSpan;
				}
			}
		}

		static public SnapshotSpan GetContinuedLines(ITextSnapshotLine line)
		{
			ITextSnapshot snapshot = line.Snapshot;

			Span lineSpan = GetContinuedLinesSpan(line.Start, line.End);

			// Generate a snapshot that spans all lines
			SnapshotPoint startPoint = snapshot.GetLineFromLineNumber(lineSpan.Start).Start;
			SnapshotPoint endPoint = snapshot.GetLineFromLineNumber(lineSpan.End).End;

			return new SnapshotSpan(startPoint, endPoint);
		}

		static public SnapshotSpan GetContinuedLines(SnapshotSpan span)
		{
			ITextSnapshot snapshot = span.Snapshot;

			Span lineSpan = GetContinuedLinesSpan(span);

			// Generate a snapshot that spans all lines
			SnapshotPoint startPoint = snapshot.GetLineFromLineNumber(lineSpan.Start).Start;
			SnapshotPoint endPoint = snapshot.GetLineFromLineNumber(lineSpan.End).End;

			return new SnapshotSpan(startPoint, endPoint);
		}

		static public Span GetContinuedLinesSpan(SnapshotSpan span)
		{
			return GetContinuedLinesSpan(span.Start, span.End);
		}

		/// <summary>
		/// Expands a given span of lines (usually one) to a virtual line that accounts for
		/// continuation characters
		/// </summary>
		static public Span GetContinuedLinesSpan(SnapshotPoint start, SnapshotPoint end)
		{
			ITextSnapshot snapshot = start.Snapshot;

			int startLineNumber = start.GetContainingLine().LineNumber;
			int endLineNumber = (end <= start.GetContainingLine().EndIncludingLineBreak) ? startLineNumber : snapshot.GetLineNumberFromPosition(end);

			// Find the first line
			bool lineContinuedUp = (startLineNumber > 0) && snapshot.GetLineFromLineNumber(startLineNumber - 1).GetText().EndsWith("\\");
			while (lineContinuedUp && (startLineNumber > 0))
			{
				startLineNumber--;
				lineContinuedUp = (startLineNumber > 0) && snapshot.GetLineFromLineNumber(startLineNumber - 1).GetText().EndsWith("\\");
			}

			// Find the last line
			while ((endLineNumber < snapshot.LineCount - 1) && (snapshot.GetLineFromLineNumber(endLineNumber).GetText().EndsWith("\\")))
			{
				endLineNumber++;
			}

			return new Span(startLineNumber, endLineNumber - startLineNumber);
		}

		public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

		#endregion


		/// <summary>
		/// Updates the Error List by clearing our items and adding any errors found in the current set of tags
		/// </summary>
		void ReparseFile(object sender, EventArgs args)
		{
			var snapshot = buffer.CurrentSnapshot;

			var spans = new NormalizedSnapshotSpanCollection(snapshot.Lines.Select(sl => new SnapshotSpan(snapshot, sl.Start.Position, sl.Length)));

			errorListProvider.Tasks.Clear();

			foreach (var spanTag in spans.SelectMany(span => analizer.GetMatches(span)))
			{
				AddErrorTask(spanTag.Span, spanTag.Tag);
			}
		}


		//private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
		//{
		//    if (e.NewSnapshot != e.OldSnapshot)
		//    {
		//        UpdateAtCaretPosition(textView.Caret.Position);
		//    }
		//}

		//private void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
		//{
		//    UpdateAtCaretPosition(e.NewPosition);
		//}

		///// <summary>
		///// As the caret moves around, ask for new tags
		///// </summary>
		//private void UpdateAtCaretPosition(CaretPosition caretPosition)
		//{
		//    currentChar = caretPosition.Point.GetPoint(buffer, caretPosition.Affinity);

		//    if (!currentChar.HasValue)
		//        return;

		//    var line = buffer.CurrentSnapshot.GetLineFromPosition(currentChar.Value.Position);

		//    OnTagsChanged(new SnapshotSpanEventArgs(new SnapshotSpan(buffer.CurrentSnapshot, line.Start.Position, line.Length)));
		//}

		//private void OnTagsChanged(SnapshotSpanEventArgs e)
		//{
		//    EventHandler<SnapshotSpanEventArgs> handler = TagsChanged;
		//    if (handler != null) handler(this, e);
		//}

		/// <summary>
		/// Add one task to the Error List based on the given tag
		/// </summary>
		private void AddErrorTask(SnapshotSpan span, IErrorTag tag)
		{
			if (errorListProvider != null)
			{
				var task = new ErrorTask
				           	{
				           		CanDelete = true,
				           		Document = document != null ? document.FilePath : string.Empty,
				           		ErrorCategory = TaskErrorCategory.Error,
				           		Text = tag.ToolTipContent.ToString(),
				           		Line = span.Start.GetContainingLine().LineNumber,
				           		Column = span.Start.Position - span.Start.GetContainingLine().Start.Position
				           	};

				task.Navigate += TaskNavigate;
				errorListProvider.Tasks.Add(task);
			}
		}

		/// <summary>
		/// Callback method attached to each of our tasks in the Error List
		/// </summary>
		private void TaskNavigate(object sender, EventArgs e)
		{
			var error = sender as ErrorTask;

			if (error == null) return;
			error.Line += 1;
			error.Column += 1;
			//errorListProvider.Navigate(error, new Guid(Constants.vsViewKindCode));
			errorListProvider.Navigate(error, new Guid("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}"));
			error.Column -= 1;
			error.Line -= 1;
		}
	}
}